/**
 * 
 */
package com.ditty.router;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ditty.annotation.ActionMapping;
import com.ditty.annotation.ClearActionInterceptor;
import com.ditty.annotation.Controller;
import com.ditty.interceptor.AbstractActionIntercptor;
import com.ditty.interceptor.Interceptors;
import com.ditty.invoke.action.Action;
import com.ditty.kit.LinkedKit;
import com.ditty.kit.StrKit;
import com.ditty.kit.ValueKit;

/**
 * @author dingnate
 *
 */
public class Routers {
	private static final Logger LOG = LoggerFactory.getLogger(Routers.class);
	private static Routers ME = new Routers();

	Routers() {
	}

	public static Routers me() {
		return ME;
	}

	private Map<String, Action> actionMapping = new HashMap<String, Action>();

	public Routers add(Class<?>... controllerClasses) {
		//global
		AbstractActionIntercptor globalInterceptor = null;
		//controller
		for (Class<?> controllerClass : controllerClasses) {
			if (!controllerClass.isAnnotationPresent(Controller.class)) {
				continue;
			}
			String controllerKey = getControllerKey(controllerClass);
			AbstractActionIntercptor controllerInterceptor = Interceptors.buildControllerInterceptor(controllerClass);
			if (!controllerClass.isAnnotationPresent(ClearActionInterceptor.class)) {
				globalInterceptor = Interceptors.me().getHead();
			}
			Method[] declaredMethods = controllerClass.getDeclaredMethods();
			for (Method method : declaredMethods) {
				if (!Modifier.isPublic(method.getModifiers()) || !method.isAnnotationPresent(ActionMapping.class)) {
					continue;
				}
				AbstractActionIntercptor actionInterceptor = Interceptors.buildActionInterceptor(method);
				//method
				if (!method.isAnnotationPresent(ClearActionInterceptor.class)) {
					actionInterceptor = LinkedKit.buildLinkedObjects(globalInterceptor, controllerInterceptor, actionInterceptor);
				}
				String actionKey;
				String[] argNames = null;
				String httpMethod = "";
				ActionMapping annotation = method.getAnnotation(ActionMapping.class);
				if (annotation != null) {
					actionKey = ValueKit.getValue(annotation.actionKey(), method.getName());
					if (!actionKey.startsWith(ValueKit.STR_SLASH)) {
						actionKey = controllerKey + ValueKit.STR_SLASH + actionKey;
					}
					argNames = annotation.argNames();
					if (argNames.length > 0 && argNames.length != method.getParameterTypes().length) {
						throw new RuntimeException("argNames count in annotation[ActionMapping] is not equal to parameters count:" + method);
					}
					httpMethod = ValueKit.getValue(annotation.httpMethod(), ValueKit.EMPTY);
				} else {
					actionKey = controllerKey + ValueKit.STR_SLASH + method.getName();
				}
				actionMapping(actionKey, new Action(httpMethod, controllerClass, method, argNames, actionInterceptor));
			}
		}
		return this;
	}

	/**
	 * @param actionKey
	 * @param httpMethod
	 * @param controllerClass
	 * @param method
	 * @param argNames
	 * @param actionInterceptor
	 */
	private void actionMapping(String actionKey, Action action) {
		Action preAction = actionMapping.get(actionKey);
		if (preAction == null) {
			actionMapping.put(actionKey, action);
			logActionMapping(actionKey, action);
			return;
		}
		String httpMethod = action.getHttpMethod();
		Action tail = preAction;
		while (preAction != null) {
			if (StrKit.isBlank(preAction.getHttpMethod()) || StrKit.isBlank(httpMethod) || StrKit.equals(preAction.getHttpMethod(), httpMethod)) {
				throw new RuntimeException(String.format("routting [%s] error, actionKey[%s] had been used by [%s]", action.getMethod().toString(),
						actionKey, preAction.getMethod().toString()));
			}
			tail = preAction;
			preAction = preAction.next;
		}
		tail.next = action;
		logActionMapping(actionKey, action);
	}

	/**
	 * @param actionKey
	 * @param action
	 */
	private void logActionMapping(String actionKey, Action action) {
		LOG.info("actionMapping:[{}]{}={}", action.getHttpMethod(), actionKey, action.getMethod().toGenericString());
	}

	/**
	 * @param controllerClass
	 * @return
	 */
	private String getControllerKey(Class<?> controllerClass) {
		ActionMapping controllerAnn = controllerClass.getAnnotation(ActionMapping.class);
		String actionKey;
		if (controllerAnn == null || StrKit.isBlank(actionKey = ValueKit.getValue(controllerAnn.actionKey(), ValueKit.EMPTY))) {
			return "";
		}
		String controllerKey = actionKey.trim();
		if (!controllerKey.startsWith(ValueKit.STR_SLASH)) {
			controllerKey = ValueKit.STR_SLASH + controllerKey;
		}
		return controllerKey;
	}

	public Action get(String httpMethod, String target, String[] urlTail) {
		Action action = actionMapping.get(target);
		if (action == null) {
			int rIndex = 0;
			if ((rIndex = StrKit.lastIndexOf(target, ValueKit.STR_QUERY)) != -1 || (rIndex = StrKit.lastIndexOf(target, ValueKit.STR_SLASH)) != -1) {
				action = actionMapping.get(target.substring(0, rIndex));
				urlTail[0] = target.substring(rIndex);
			}
		}
		while (action != null) {
			if (StrKit.isBlank(action.getHttpMethod()) || StrKit.equals(action.getHttpMethod(), httpMethod)) {
				return action;
			}
			action = action.next;
		}
		return null;
	}
}
